package com.psychologyCommunity.Community.service;


import com.mysql.cj.util.StringUtils;
import com.psychologyCommunity.Community.dao.LoginTicketMapper;
import com.psychologyCommunity.Community.dao.UserMapper;
import com.psychologyCommunity.Community.entity.LoginTicket;
import com.psychologyCommunity.Community.entity.Page;
import com.psychologyCommunity.Community.entity.User;
import com.psychologyCommunity.Community.util.CommunityConstance;
import com.psychologyCommunity.Community.util.CommunityUtil;
import com.psychologyCommunity.Community.util.MailClient;
import com.psychologyCommunity.Community.util.RedisKeyUtil;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class UserService implements CommunityConstance {
    @Autowired
    private UserMapper userMapper;
    public User findUserById(int id){//这个之前写过
//        return  userMapper.selectById(id);
        //优化 ：先从 缓存中查 ，没有就初始化缓存，如果有 就直接返回
        User cacheUser = getCache(id);
        if(cacheUser==null){
            cacheUser=initCache(id);
        }
        return cacheUser;
    }
    //因为注册 是针对的用户表的操作，所以我们可以把这个逻辑写到用户表中去
    //注册的过程中药发邮件，所以把邮件客户端 、模板引擎都注入进来
    @Autowired
    private MailClient mailClient;
    @Autowired
    private TemplateEngine templateEngine;

    //把域名和项目名都注册进来
    @Value("${community.path.domain}")
    private  String domain;
    @Value("${server.servlet.context-path}")
    private  String contextPath;
    //注册的时候 你需要把用户的信息：账号 密码 邮箱 都传给我
    public Map<String,Object> register(User user) throws IllegalAccessException {
        Map<String,Object> map=new HashMap<String, Object>();
        //空值处理
        if(user ==null){
            throw new IllegalAccessException("参数不能为空");
        }
        //如果他的账号是空的，这次就不报错 ，因为账号是空的，是业务上的没找到
        //应该把它封装到Map中 ，告诉客户 你这样做不对，提示账号不能为null
        if(StringUtils.isNullOrEmpty(user.getUsername())){
           map.put("usernameMessage","账号不能为空！");
           return map;
        }
        //邮箱也有可能为空 也要做处理
        if(StringUtils.isNullOrEmpty(user.getEmail())){
            map.put("EmailMessage","邮箱不能为空！");
            return map;
        }
        //先验证账号是否存在（去数据库里面查一下 看有没有）-有就不为空
        User uInfo = userMapper.selectByName(user.getUsername());
        if(uInfo!=null){ //如果不为空 --该账号如果存在 就不能再注册了
            map.put("usernameMessage","该账号已经存在了！");
            return map;
        }
        //验证邮箱 是否存在 如果存在就提示它换一个注册
        User uE = userMapper.selectByEmail(user.getEmail());
        if(uInfo!=null){ //如果返回的用户不为空 --存在 就不能再注册了
            map.put("EmailMessage","该邮箱已经被用户注册了！");
            return map;
        }

        //注册用户     //经过以上账号和邮箱为空 和已注册的过滤 现在就可以开始注册了
        //加密 +随机字符串，不需要那么长  5位就行了 存到salt中
        user.setSalt(CommunityUtil.generateUUID().substring(0,5));
        //设置密码 用户原密码 +salt中的值
        user.setPassword(CommunityUtil.md5(user.getPassword()+user.getSalt()));
        //注册的用户 默认都是普通用户 type=0 ，状态都是未激活status=0
        user.setType(0);
        user.setStatus(0);
        //未激活就给他 一个激活码 这个可以长一些
        user.setActivationCode(CommunityUtil.generateUUID());

        //头像给他一个默认的牛客网中的路径 有1001多个图片 我们可以随机给他分
        // （这也可进行后期的改进） images.nowcoder.com/head/1t.png 替换1 就可得到不同的图片
        // new Random().nextInt(1000))表示生成1000个 随机0-1000的随机整数 替换%d 就能实现生成随机的头像
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png",new Random().nextInt(1000)));

        user.setCreateTime(new Date());
        //最后把该用户添加到库里
        userMapper.insertUser(user);

        //添加了之后需要给 用户发送一个激活邮件 html 文件 已经写好了 activatio.html
        Context context=new Context();
        context.setVariable("email",user.getEmail());

        //希望服务器用什么路径 去处理这个请求 http://localhost:8888/community/activation/用户id/激活码 要拼出来
        //注册时 用户传进来的user是没有userid的，当调用insert语句之后 这个user里面就有id了
        // 这是mybatis对他进行了回填，底层在执行insert的时候会自动生成id 赋值给usrid
        String url=domain+contextPath+ "/activation/" +user.getId()+"/"+user.getActivationCode();
        context.setVariable("url",url);
        //调用模板模板引擎 生成邮件的内容
        String con1=templateEngine.process("/mail/activation",context);
        //最后一步就是调用 邮件客户端去 发送邮件  传入参数 发邮件发给谁  标题 内容
        mailClient.mailSender(user.getEmail(),"激活账号",con1);
        return map;
    }
    //激活请求
    //点击链接 页面得到的url 可以看到上面发的邮件中我们存入了 用户id/激活码 的值
    //第三次向 服务器 发送请求 带着userid 和激活码
    public int activation(int userId,String code){
        User user=userMapper.selectById(userId);//拿到对应的user对象
        if(user.getStatus() ==1){ //因为用户注册时 状态都是设置的0 可以看上面，如果这个用户的状态已经是1了就说明已经激活过了
            return ACTIVATION_REPEAT;//记得类上面实现接口 才可以直接用接口中定义的常量
        }else if(user.getActivationCode().equals(code)){//如果激活码和 传进来的激活码一致 就说明是可以激活的
            userMapper.updateStatus(userId,1); //更新用户之后 就直接清缓存
            //优化--只要是调用了update方法 即用户被修改 就得请缓存
            clearCache(userId);
            //就更新用户激活状态,并返回更新成功
            return ACTIVATION_SUCCESS;
        }else {
            return ACTIVATION_FAILURE;//剩下就是 激活不等的情况 就直接返回激活失败
        }
    }
//    @Autowired
//    private UserMapper userMapper;
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private LoginTicketMapper loginTicketMapper;
    //实现登录功能；登录时 可能成功或者失败（比如说 账号未输入、账号不存在、账号未激活。。。。。）
    //返回值 可能有多种情况和结果，就建一个Map保存
    //用户在页面输入的密码是明文（就是没有加密过的密码），但是注册的时候 存的是加密后的密码，
    // 所以传入的形参password不能直接和库里面的密码比，因此 我也得把传入的密码 加密了比较，加密后的结果是固定的
    //expiredSecond表示过期的秒数，传入一个 用户希望的 这个凭证需要过期的时间（有效期）
    public Map<String,Object> login(String username,String password,int expiredSecond){
        Map<String,Object> map=new HashMap<String, Object>();

        //首先做一些空值的处理
        if(StringUtils.isNullOrEmpty(username)){
            map.put("usernameMsg","账号不能为空");
            return  map;
        }

        if(StringUtils.isNullOrEmpty(password)){
            map.put("passwordMsg","密码不能为空");
            return map;
        }

        //验证账号
        User user=userMapper.selectByName(username);//查看用户表里面有没有这个用户名的用户
        if(user==null){
            map.put("usernameMsg","该账号不存在");
            return  map;
        }
        //验证状态
        if (user.getStatus()==0){//有账号但没激活
            map.put("usernameMsg","该账号未激活");
            return map;
        }
        //验证密码
        password=CommunityUtil.md5(password+user.getSalt());//密码先加密再比较
        if(!user.getPassword().equals(password)){ //注意这是用的equals
            //密码不相等，直接返会提示信息
            map.put("passwordMsg","密码不正确");
            return map;
        }

        //如果没有在上面的各种情况返回 就说明是能成功登录的
        //生成登录凭证
        LoginTicket loginTicket=new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        //expiredSecond 是毫秒 ，换算成 s 即有效期是多少秒
        loginTicket.setExpired(new Date(System.currentTimeMillis()+expiredSecond*1000));

//        //把生成好的用户登录凭证存进去 --之前是存在mysql中
//        loginTicketMapper.insertLoginTicket(loginTicket);
        //优化：存在redis中
        String redisKey= RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        //每个人都对应一个登录凭证，redis就会把loginTicket对象序列化成一个Json格式的字符串 保存
        redisTemplate.opsForValue().set(redisKey,loginTicket);

        //最终返会的结果里 就把登录凭证 放到map中，但是其实浏览器只需要拿到key，表示这是一个登录的凭证
        //当浏览器下次再访问浏览器的时候，把这个key给服务器，服务器去库里找这条数据 如果没问题（状态对、时间也对）
        //我就认为你登陆成功 我们通过 ticket也能找到用户是谁；
        //login_ticket这个表起到的作用就有点类似于session，我们此例子没有用session 直接用表存的
        map.put("ticket",loginTicket.getTicket());
        return map;

    }
    public void logOut(String ticket){
//        loginTicketMapper.updateStatus(ticket,1);
        //优化：
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        //拿到登录凭证，把它的状态改为1  表示删除状态 --再存回去
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey,loginTicket);//把新改变的值 覆盖给了 旧值

    }

    //追加一个 查询凭证的方法 通过拿到的ticket（随机字符串），取这条登陆凭证的信息--里面就包含了uerId
    public LoginTicket findLoginTicket(String ticket){
//      return   loginTicketMapper.selectByTicket(ticket); --原来是利用LoginTicketMapper这个组件 调用里面查询的方法
        //优化，从 redis中查
        String redisKey=RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

    public int updateHeader(int userId,String HeaderUrl){
//       return userMapper.updateHeader(userId,HeaderUrl);
//        优化：
        int rows = userMapper.updateHeader(userId, HeaderUrl);
        clearCache(userId);
        return rows;
    }


    //通过用户名来找到用户
    public User findUserByName(String username){
        return userMapper.selectByName(username);
    }

    //1.优先从缓存中取值
    private  User getCache(int userId){
        String redisKey = RedisKeyUtil.getUserKey(userId);//通过userId 拿到这个rediskey
        return (User) redisTemplate.opsForValue().get(redisKey);

    }
    //2从缓存中取不到数据时 初始化缓存数据
    private User initCache(int userId){
        User user = userMapper.selectById(userId);//从数据库中拿到用户
        String redisKey = RedisKeyUtil.getUserKey(userId); //根据userId得到用户在redis中的 redisKey
        redisTemplate.opsForValue().set(redisKey,user,3600, TimeUnit.SECONDS);//给一个过期时间，因为是缓存 --这给的1小时
        return user;
    }
    //3.数据变更时清除缓存数据
    private void clearCache(int userId){
        String userKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(userKey);//直接删掉redis中的key  就删掉了
    }

    //最终我们想把用户的权限存到SecurityContext里面，根据用户获得用户的权限，得加这个方法
    public Collection<? extends GrantedAuthority> getAuthorities(int userId){
        User user = this.findUserById(userId);
        List<GrantedAuthority> list=new ArrayList<>();
        list.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                switch (user.getType()){
                    case 1:
                        return AUTHORITY_ADMIN;
                    case 2:
                        return AUTHORITY_MODERATOR;
                    default:
                        return AUTHORITY_USER;//默认返回普通用户
                }
            }
        });
        return list;

        //我们登录成功之后，会返回一个Tickit，让用户去存在服务器上，服务器回去验证这个Tickit，
        // 看你过没过期，看你对没对，登录成没成功？
        //在拦截器中我们就知道 登录是否有效，如果登录有效就意味着 是有权限的用户，
        // 查这个用户的权限再去存到对象里，后续就能用它去授权了
    }
}
